const path = require("path");
const dayjs = require("dayjs");
// 导入 express 库
const express = require("express");
// 使用 express-async-errors 弥补 express 无法全局处理异步异常的问题。
require("express-async-errors");

// 使用 multer 处理文件上传
const multer = require("multer");
const { v4: uuid } = require("uuid");
// 配置上传文件的存储位置和文件名
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "./public/avatars");
  },
  filename: function (req, file, cb) {
    const ext = path.extname(file.originalname);
    cb(null, uuid() + ext);
  },
});
const upload = multer({ storage });

// 读取配置文件
const fs = require("fs");
const config = JSON.parse(fs.readFileSync("config.json").toString());
// 初始化 orm 对象
const { Sequelize, Op, QueryTypes } = require("sequelize");
const {
  database,
  username,
  password,
  dialect = "mysql",
  port = 3306,
  serverPort = 5000,
} = config;
const sequelize = new Sequelize(database, username, password, {
  host: "localhost",
  dialect: dialect,
  port: port,
  define: {
    freezeTableName: true,
    underscored: true,
    timestamps: false,
  },
  dialectOptions: {
    dateStrings: true,
    typeCast: true,
  },
  timezone: "+08:00",
  pool: {
    max: 10,
    min: 1,
    acquire: 30 * 1000,
    idle: 30 * 1000,
  },
});
// 初始化模型对象
// User 对象和 Todo 对象的模型定义在 models.js 中。
// 详细内容请参阅:
const { initModels, User, Todo } = require("./models");
initModels(sequelize);

// 实例化 express application 对象
const app = express();
// cors (Cross-Origin Resource Sharing) 即跨域资源共享
const cors = require("cors");
app.use(
  cors({
    origin: "http://localhost:3000",
    allowedHeaders: [
      "Content-Type",
      "Content-Length",
      "Authorization",
      "Origin",
      "Accept",
      "X-Requested-With",
    ],
    methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    credentials: true,
  })
);

// static: 设置静态文件服务的位置
app.use(express.static("public"));

// template engine: 设置模版引擎和模版文件的位置
app.set("views", "./views");
app.set("view engine", "ejs");

// body parse: 使用中间件解析请求对象(request)的body属性
//  请注意body的内容是由客户端端发起请求时决定的
//  在本例中，服务器应用 app 对象
//  通过 express.json() 和 express.urlencoded() 中间件支持了两种不同格式的解析。
app.use(express.json()); // for parsing application/json
app.use(express.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

// session: 处理连接的会话对象 session
const session = require("express-session");
// 信任代理
app.set("trust proxy", 1);
app.use(
  session({
    secret: "2l64031j3c846=06ohd64a1l623",
    resave: true,
    saveUninitialized: true,
    cookie: {
      secure: false,
      httpOnly: false,
      maxAge: 60 * 1000 * 30,
    },
  })
);
// 定义验证器，使用 express-validator 中间件实现对请求的参数进行验证。
const { body, param, query, validationResult } = require("express-validator");
const validator = {
  username: body("username")
    .trim()
    .isLength({ min: 3, max: 20 })
    .withMessage("用户名长度必须在3到20个之间"),
  password: body("password")
    .trim()
    .isLength({ min: 6, max: 20 })
    .withMessage("密码长度在6到20个之间"),
  confirmPassword: body("confirmPassword")
    .trim()
    .isLength({ min: 6, max: 20 })
    .withMessage("密码长度在6到20个之间")
    .custom((val, { req }) => {
      if (val != req.body.password) throw new Error("两次密码输入必须一致");
      return true;
    }),
  nickname: body("nickname")
    .notEmpty()
    .withMessage("昵称不能为空")
    .isLength({ max: 50 })
    .withMessage("昵称不能超过50个字符"),
  title: body("title")
    .notEmpty()
    .withMessage("标题不能为空")
    .isLength({ max: 100 })
    .withMessage("标题不能超过100个字符")
    .trim()
    .escape(),
  description: body("description")
    .isLength({ max: 255 })
    .withMessage("待办描述不应该超过255个字符")
    .trim()
    .escape(),
  priority: body("priority")
    .notEmpty()
    .isIn(["normal", "important", "crucial"]),
  todoId: param("tid").notEmpty().toInt(),
  limit: query("limit").optional().toInt(),
  offset: query("offset").optional().toInt(),
};
// 验证方法
const validate = (validators) => {
  return async (req, res, next) => {
    await Promise.all(validators.map((v) => v.run(req)));
    const errors = validationResult(req);
    if (errors.isEmpty()) {
      return next();
    }
    let err = {};
    for (let e of errors.array()) {
      if (e.param in err) err[e.param].push(e.msg);
      else err[e.param] = [e.msg];
    }
    res.json({ success: false, errors: err });
  };
};

const dateRange = (days, start) => {
  if (!start) start = dayjs().subtract(days, "day");
  return [start.toDate(), start.add(days, "day").toDate()];
};

app.post(
  "/register",
  upload.single("avatar"),
  validate([
    validator.username,
    validator.password,
    validator.confirmPassword,
    validator.nickname,
  ]),
  async (req, res) => {
    let { username, password, nickname } = req.body;
    // 检查用户名是否重复
    let user = await User.findOne({ where: { username } });
    if (user) {
      return res.json({
        success: false,
        errors: { username: ["用户名已经存在！"] },
      });
    }
    // 保存注册用户信息
    user = await User.create({
      username,
      password,
      nickname,
      avatar: req.file ? req.file.filename : null,
    });
    res.json({ success: true, object: user.toPlain() });
  }
);

app.post(
  "/login",
  validate([validator.username, validator.password]),
  async (req, res) => {
    let { username, password } = req.body;
    let user = await User.findOne({ where: { username: username } });
    if (user && user.checkPassword(password)) {
      req.session.userId = user.id;
      res.json({ success: true, user: user.toPlain() });
    } else {
      res.json({ success: false, message: "用户名或密码错误！" });
    }
  }
);

app.get("/logout", (req, res) => {
  req.session.userId = undefined;
  res.json({ success: true });
});
/**
 * stat 使用SQL执行查询语句
 *
 * @param {string} query 查询语句
 * @param {array} values 值（数组）
 * @param {boolean} plain
 * @returns object
 */
const stat = async (query, values = [], plain = true) => {
  return await sequelize.query(
    {
      query,
      values,
    },
    { type: QueryTypes.SELECT, plain, raw: true }
  );
};

let router = express.Router();
router
  // 验证用户登录中间件
  .use(async (req, res, next) => {
    if (req.session.userId == undefined) {
      return res
        .status(401)
        .json({ success: false, message: "用户尚未登录！" });
    }
    req.user = await User.findByPk(req.session.userId);
    next();
  })
  // 获取用户信息
  .get("/user/info", async (req, res) => {
    res.json({ success: true, user: req.user.toPlain() });
  })
  // 获取统计信息
  .get("/user/stat", async (req, res) => {
    let { count } = await stat(
        'select count(id) as count from "Todo" where user_id = ?',
        [req.user.id]
      ),
      finished = await stat(
        `select priority, count(id) as count from "Todo"
          where user_id = ? and finished_at is not null
          group by priority`,
        [req.user.id],
        false
      ),
      notFinish = await stat(
        `select priority, count(id) as count from "Todo"
          where user_id = ? and finished_at is null
          group by priority `,
        [req.user.id],
        false
      );
    res.json({
      success: true,
      rs: { count, finished, notFinish },
    });
  })
  // 获取登录用户的待办列表
  .get(
    "/todos",
    validate([validator.limit, validator.offset]),
    async (req, res) => {
      let { days, priority, isFinished, limit = 10, offset = 0 } = req.query,
        where = {};
      // 添加了按创建时间、优先级和完成与否的查询条件
      if (days && days !== "all")
        where["created_at"] = { [Op.between]: dateRange(parseInt(days)) };
      if (priority && priority !== "all") where["priority"] = priority;
      if (isFinished && isFinished !== "all") {
        where["finished_at"] =
          isFinished == "true" ? { [Op.not]: null } : { [Op.is]: null };
      }
      let conditions = {
        where,
        order: [["created_at", "desc"]],
      };
      res.json({
        success: true,
        query: {
          count: await req.user.countTodos({ where }),
          rs: await req.user.getTodos({
            ...conditions,
            limit,
            offset,
          }),
          limit,
          offset,
        },
      });
    }
  )
  // 添加新待办
  .post(
    "/todos",
    validate([validator.title, validator.description, validator.priority]),
    async (req, res) => {
      let todo = await req.user.createTodo(req.body);
      res.json({ success: true, todo: await todo.reload() });
    }
  )
  // 删除待办
  .delete("/todos/:tid", validate([validator.todoId]), async (req, res) => {
    await Todo.destroy({
      where: { id: req.params.tid, UserId: req.session.userId },
    });
    res.json({ success: true });
  })
  // 更新待办
  .put(
    "/todos/:tid",
    validate([
      validator.todoId,
      validator.title,
      validator.description,
      validator.priority,
    ]),
    async (req, res) => {
      let todo = await Todo.findByPk(req.params.tid);
      if (todo.finished_at != null) {
        return res.json({
          success: false,
          message: "该待办已经完成，不可更改信息！",
        });
      }
      todo.update(req.body);
      res.json({ success: true, todo });
    }
  )
  // 完成待办
  .put("/finish/todos/:tid", validate([validator.todoId]), async (req, res) => {
    let todo = await Todo.findByPk(req.params.tid);
    if (todo.finished_at) {
      return res.json({ success: false, message: "该待办已经完成！" });
    }
    todo.finished_at = new Date();
    await todo.save();
    res.json({ success: true, todo });
  });
// 挂接路由
app.use("/", router);
// 全局错误处理
app.use((err, req, res, next) => {
  console.log(err);
  res.status(500).json({ success: false, error: err.message });
  next(err);
});
// 监听端口，开启http服务
app.listen(serverPort, () => {
  console.log(`Server started and listening at http://localhost:${serverPort}`);
  // 数据库结构同步
  sequelize.sync().then(() => {
    console.log("Database sync success ...");
  });
});
// 监听操作系统信号，关闭数据库连接。
process.on("SIGINT", () => {
  sequelize.close().then(() => {
    console.log("close connection success ...");
    process.exit();
  });
});
